//  Copyright (c) Microsoft Corporation.  All Rights Reserved.

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Security.Cryptography.X509Certificates;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;

namespace Saml20
{
    public class Saml20Serializer
    {
        public virtual Saml20SecurityToken ReadToken(XmlReader reader, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");

            if (keyInfoSerializer == null)
                throw new ArgumentNullException("keyInfoSerializer");

            if (keyInfoSerializer == null)
                throw new ArgumentNullException("keyInfoSerializer");

            if ((reader.LocalName == Saml20Constants.Assertion) || (reader.LocalName == Saml20Constants.Namespace))
            {
                Saml20Assertion assertion = LoadAssertion(reader, keyInfoSerializer, outOfBandTokenResolver);
                if (assertion == null)
                    throw new Exception("Unable to load SAML Assertion.");

                return new Saml20SecurityToken(assertion);
            }
            else
            {
                Saml20EncryptedAssertion encAssertion = LoadEncryptedAssertion(reader, keyInfoSerializer, outOfBandTokenResolver);
                if (encAssertion == null)
                    throw new Exception("Unable to load SAML Assertion.");

                return new Saml20SecurityToken(encAssertion);
            }
        }

        public virtual void WriteToken(Saml20SecurityToken token, XmlWriter writer, SecurityTokenSerializer keyInfoSerializer)
        {
            if (token == null)
                throw new ArgumentNullException("token");

            if (writer == null)
                throw new ArgumentNullException("writer");

            if (keyInfoSerializer == null)
                throw new ArgumentNullException("keyInfoSerializer");

            if (token.EncryptedAssertion != null)
                token.EncryptedAssertion.WriteXml(writer, keyInfoSerializer);
            else
                token.Assertion.WriteTo(writer, keyInfoSerializer);
        }

        public Saml20Advice LoadAdvice(XmlReader reader, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            return new Saml20Advice(reader, this, keyInfoSerializer, outOfBandTokenResolver);
        }

        public Saml20EncryptedAssertion LoadEncryptedAssertion(XmlReader reader, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            return new Saml20EncryptedAssertion(reader, this, keyInfoSerializer, outOfBandTokenResolver);
        }

        public Saml20Assertion LoadAssertion(XmlReader reader, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            return new Saml20Assertion(reader, this, keyInfoSerializer, outOfBandTokenResolver);
        }

        public Saml20Condition LoadCondition(XmlReader reader, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            if (reader.IsStartElement(Saml20Constants.OneTimeUse, Saml20Constants.Namespace))
            {
                return new Saml20OneTimeUse(reader, this, keyInfoSerializer, outOfBandTokenResolver);
            }

            throw new ArgumentException(String.Format("The condition {0} is not understood. Custom conditions require custom serializer to be created.", reader.LocalName));
        }

        public Saml20Conditions LoadConditions(XmlReader reader, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            return new Saml20Conditions(reader, this, keyInfoSerializer, outOfBandTokenResolver);
        }

        public Saml20Subject LoadSubject(XmlReader reader, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            return new Saml20Subject(reader, this, keyInfoSerializer, outOfBandTokenResolver);
        }

        public Saml20Statement LoadStatement(XmlReader reader, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            if (reader.IsStartElement(Saml20Constants.AuthnStatement, Saml20Constants.Namespace))
                return new Saml20AuthnStatement(reader, this, keyInfoSerializer, outOfBandTokenResolver);
            if (reader.IsStartElement(Saml20Constants.AuthzDecisionStatement, Saml20Constants.Namespace))
                return new Saml20AuthzDecisionStatement(reader, this, keyInfoSerializer, outOfBandTokenResolver);
            if (reader.IsStartElement(Saml20Constants.AttributeStatement, Saml20Constants.Namespace))
                return new Saml20AttributeStatement(reader, this, keyInfoSerializer, outOfBandTokenResolver);

            throw new SecurityTokenException("Custom statements found. Please register a custom serializer to handle custom statements.");
        }

        public Saml20SubjectConfirmation LoadSubjectConfirmation(XmlReader reader, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            return new Saml20SubjectConfirmation(reader, this, keyInfoSerializer, outOfBandTokenResolver);
        }

        public Saml20SubjectConfirmationData LoadSubjectConfirmationData(XmlReader reader, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            Saml20SubjectConfirmationData subjectConfirmationData = new Saml20SubjectConfirmationData();
            subjectConfirmationData.ReadXml(reader, this, keyInfoSerializer, outOfBandTokenResolver);

            return subjectConfirmationData;
        }

        public Saml20Attribute LoadAttribute(XmlReader reader, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            return new Saml20Attribute(reader, this, keyInfoSerializer, outOfBandTokenResolver);
        }

        public Saml20EncryptedAttribute LoadEncryptedAttribute(XmlReader reader, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            return new Saml20EncryptedAttribute(reader, this, keyInfoSerializer, outOfBandTokenResolver);
        }

        // Helper metods to read and write SecurityKeyIdentifiers.
        internal static SecurityKeyIdentifier ReadSecurityKeyIdentifier(XmlReader reader, SecurityTokenSerializer tokenSerializer)
        {
            if (tokenSerializer == null)
                throw new ArgumentNullException("tokenSerializer");

            if (tokenSerializer.CanReadKeyIdentifier(reader))
            {
                return tokenSerializer.ReadKeyIdentifier(reader);
            }

            throw new InvalidOperationException("Unable to reader Security key Identifier.");
        }

        internal static void WriteSecurityKeyIdentifier(XmlWriter writer, SecurityKeyIdentifier ski, SecurityTokenSerializer tokenSerializer)
        {
            if (tokenSerializer == null)
                throw new ArgumentNullException("tokenSerializer");

            bool keyWritten = false;
            if (tokenSerializer.CanWriteKeyIdentifier(ski))
            {
                tokenSerializer.WriteKeyIdentifier(writer, ski);
                keyWritten = true;
            }

            if (!keyWritten)
                throw new InvalidOperationException("Unable to write Security key identifier.");
        }

        internal static SecurityKey ResolveSecurityKey(SecurityKeyIdentifier ski, SecurityTokenResolver tokenResolver)
        {
            if (ski == null)
                throw new ArgumentNullException("ski");

            if (tokenResolver != null)
            {
                for (int i = 0; i < ski.Count; ++i)
                {
                    SecurityKey key = null;
                    if (tokenResolver.TryResolveSecurityKey(ski[i], out key))
                        return key;
                }
            }

            if (ski.CanCreateKey)
                return ski.CreateKey();

            return null;
        }

        internal static SecurityToken ResolveSecurityToken(SecurityKeyIdentifier ski, SecurityTokenResolver tokenResolver)
        {
            SecurityToken token = null;

            if (tokenResolver != null)
            {
                tokenResolver.TryResolveToken(ski, out token);
            }

            if (token == null)
            {
                // Check if this is a RSA key.
                RsaKeyIdentifierClause rsaClause;
                if (ski.TryFind<RsaKeyIdentifierClause>(out rsaClause))
                    token = new RsaSecurityToken(rsaClause.Rsa);
            }

            if (token == null)
            {
                // Check if this is a X509RawDataKeyIdentifier Clause.
                X509RawDataKeyIdentifierClause rawDataKeyIdentifierClause;
                if (ski.TryFind<X509RawDataKeyIdentifierClause>(out rawDataKeyIdentifierClause))
                    token = new X509SecurityToken(new X509Certificate2(rawDataKeyIdentifierClause.GetX509RawData()));
            }

            return token;
        }
    }
}
